﻿/*
 * shaim
 * http://shaim.net
 * Copyright ©2005-2008, shaim dev team
 * Licensed under the Massachusetts Institute of Technology (MIT) License
 * http://www.opensource.org/licenses/mit-license.php
 * 
 */

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace csammisrun.shaim.ContactListInternal
{
    /// <summary>
    /// A generic list that supports filtering and sorting based on generic predicates and comparisons
    /// </summary>
    /// <typeparam name="T">Typically, <see cref="MetaContact"/> or <see cref="ContactGroup"/></typeparam>
    public class BindingListCollection<T> : BindingList<T>, INotifyPropertyChanged where T : class, IOrderableElement
    {
        private bool isSorted;
        private bool isFiltered;
        private IUIDispatcher uiDispatcher;
        /// <summary>
        /// The underlying list of manually inserted items
        /// </summary>
        private readonly List<T> unsortedList;
        /// <summary>
        /// The sorting comparison currently in use
        /// </summary>
        private Comparison<T> currentComparison;
        /// <summary>
        /// The filtering predicate currently in use
        /// </summary>
        private Predicate<T> currentFilter;
        /// <summary>
        /// The current number of items which are online (for T = MetaContact)
        /// </summary>
        private int onlineCount;
        /// <summary>
        /// The index at which the item was inserted by the list controller
        /// </summary>
        private readonly Dictionary<T, int> insertIndicies;
        /// <summary>
        /// A value indicating the dirty status of the list
        /// </summary>
        private bool needsBindingRefresh;

        /// <summary>
        /// Initializes a new BindingListCollection&lt;T&gt;
        /// </summary>
        /// <param name="dispatcher">An <see cref="IUIDispatcher"/> used to delegate databinding onto the UI thread</param>
        public BindingListCollection(IUIDispatcher dispatcher)
        {
            uiDispatcher = dispatcher;
            unsortedList = new List<T>();
            insertIndicies = new Dictionary<T, int>();
        }

        #region Properties
        /// <summary>
        /// Return the total number of items in the list, visible or not
        /// </summary>
        public int TotalCount
        {
            get
            {
                if (isSorted || isFiltered)
                {
                    return unsortedList.Count;
                }
                else
                {
                    return Items.Count;
                }
            }
        }

        /// <summary>
        /// Gets a read-only view of the underlying list without filtration or sorting
        /// </summary>
        public ReadOnlyCollection<T> UnderlyingList
        {
            get
            {
                if (isFiltered || isSorted)
                {
                    return new ReadOnlyCollection<T>(unsortedList);
                }
                return new ReadOnlyCollection<T>(Items);

            }
        }

        /// <summary>
        /// Gets the current number of metacontacts with an online status in this list
        /// </summary>
        public int OnlineCount
        {
            get
            {
                return onlineCount;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether or not the list's bindings need to be refreshed
        /// </summary>
        internal bool NeedsBindingRefresh
        {
            get { return needsBindingRefresh; }
            set { needsBindingRefresh = value; }
        }

        /// <summary>
        /// Gets a value indicating whether ListChanged events are enabled.
        /// </summary>
        protected override bool SupportsChangeNotificationCore
        {
            get { return true; }
        }

        /// <summary>
        /// Gets a value indicating whether the list supports searching.
        /// </summary>
        protected override bool SupportsSearchingCore
        {
            get { return true; }
        }

        /// <summary>
        /// Gets a value indicating whether the list supports sorting.
        /// </summary>
        protected override bool SupportsSortingCore
        {
            get { return true; }
        }

        /// <summary>
        /// Gets a value indicating whether the list is sorted.
        /// </summary>
        protected override bool IsSortedCore
        {
            get { return isSorted; }
        }

        /// <summary>
        /// Gets or sets an <see cref="IUIDispatcher"/> to handle list modification calls
        /// </summary>
        internal IUIDispatcher UIDispatcher
        {
            get { return UIDispatcher; }
            set { uiDispatcher = value; }
        }
        #endregion

        #region Methods

        #region Inserting items
        /// <summary>
        /// A single object used to carry item insertion data
        /// </summary>
        private struct InsertItemData
        {
            public readonly int Index;
            public readonly T Item;

            /// <summary>
            /// Initializes a new InsertItemData
            /// </summary>
            public InsertItemData(int index, T item)
            {
                Index = index;
                Item = item;
            }
        }

        /// <summary>
        /// Provides a delegate for dispatching an item insertion
        /// </summary>
        private delegate void InsertItemDispatchHandler(InsertItemData data);

        /// <summary>
        /// Inserts the specified item in the list at the specified index.
        /// </summary>
        protected override void InsertItem(int index, T item)
        {
            lock (Items)
            {
                InsertItemInternal(new InsertItemData(index, item));
            }
        }

        private void InsertItemInternal(InsertItemData data)
        {
            if (uiDispatcher == null || uiDispatcher.CheckAccess())
            {
                //lock (this)
                {
                    int insertindex = data.Index;

                    int requestedInsertIndex = data.Item.PreferredOrder;
                    if (requestedInsertIndex < 0)
                    {
                        requestedInsertIndex = 0;
                    }
                    IList<T> currentList = (isSorted || isFiltered) ? unsortedList : Items;

                    while (requestedInsertIndex < currentList.Count
                      && currentList[requestedInsertIndex].PreferredOrder == data.Item.PreferredOrder)
                    {
                        requestedInsertIndex++;
                    }

                    // Maintain the backing list
                    if (requestedInsertIndex > unsortedList.Count)
                    {
                        unsortedList.Add(data.Item);
                    }
                    else
                    {
                        unsortedList.Insert(requestedInsertIndex, data.Item);
                    }

                    // If the list is sorted, find the correct insertion point for the sorted order
                    if (isSorted || isFiltered)
                    {
                        insertindex = PlaceByCurrentComparison(data.Item);
                    }
                    else
                    {
                        insertindex = requestedInsertIndex;
                    }

                    // Add the requested index to the item-index map
                    insertIndicies[data.Item] = requestedInsertIndex;

                    INotifyPropertyChanged propitem = data.Item as INotifyPropertyChanged;
                    if (propitem != null)
                    {
                        propitem.PropertyChanged -= item_PropertyChanged;
                        propitem.PropertyChanged += item_PropertyChanged;
                    }
                    if (insertindex != -1)
                    {
                        if (insertindex > base.Count)
                        {
                            insertindex = base.Count;
                        }
                        base.InsertItem(insertindex, data.Item);
                    }

                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("Items"));
                    }

                    // Fire the AddingNew event
                    OnAddingNew(new AddingNewEventArgs(data.Item));
                }
            }
            else
            {
                uiDispatcher.Dispatch(new InsertItemDispatchHandler(InsertItemInternal), data);
            }
        }
        #endregion

        #region Removing items
        /// <summary>
        /// A single object used to carry item removal data
        /// </summary>
        private struct RemoveItemData
        {
            public readonly T Item;

            /// <summary>
            /// Initializes a new RemoveItemData
            /// </summary>
            public RemoveItemData(T item)
            {
                Item = item;
            }
        }

        /// <summary>
        /// Provides a delegate for dispatching an item removal
        /// </summary>
        private delegate void RemoveItemDispatchHandler(RemoveItemData data);

        /// <summary>
        /// Removes an item from the collection
        /// </summary>
        public new void Remove(T item)
        {
            lock (Items)
            {
                RemoveItemInternal(new RemoveItemData(item));
            }
        }

        /// <summary>
        /// Removes an item from the collection at the specified index
        /// </summary>
        public new void RemoveAt(int index)
        {
            RemoveItem(index);
        }

        /// <summary>
        /// Removes an item from the collection at the specified index
        /// </summary>
        /// <param name="index">The index of the item to remove</param>
        protected override void RemoveItem(int index)
        {
            lock (Items)
            {
                RemoveItemInternal(new RemoveItemData(Items[index]));
            }
        }

        /// <summary>
        /// Performs the act of removing an item from the list
        /// </summary>
        private void RemoveItemInternal(RemoveItemData data)
        {
            if (uiDispatcher == null || uiDispatcher.CheckAccess())
            {
                //lock (this)
                {
                    INotifyPropertyChanged propitem = data.Item as INotifyPropertyChanged;
                    if (propitem != null)
                    {
                        propitem.PropertyChanged -= item_PropertyChanged;
                    }

                    // Remove the item from the item-index map
                    insertIndicies.Remove(data.Item);
                    // Remove the item from the unsorted list
                    unsortedList.Remove(data.Item);

                    // Remove the item from the displayed list, if it's there
                    int liveIndex = IndexOf(data.Item);
                    if (liveIndex != -1)
                    {
                        base.RemoveItem(liveIndex);
                    }

                    if (PropertyChanged != null)
                    {
                        PropertyChanged(this, new PropertyChangedEventArgs("TotalCount"));
                    }
                }
            }
            else
            {
                uiDispatcher.Dispatch(new RemoveItemDispatchHandler(RemoveItemInternal), data);
            }
        }
        #endregion

        #region Moving items
        /// <summary>
        /// A single object used to carry item movement data
        /// </summary>
        private struct MoveWithinListData
        {
            public readonly T Item;

            /// <summary>
            /// Initializes a new MoveWithinListData
            /// </summary>
            public MoveWithinListData(T item)
            {
                Item = item;
            }
        }

        /// <summary>
        /// Provides a delegate for dispatching an item move
        /// </summary>
        private delegate void MoveWithinListDispatchHandler(MoveWithinListData data);

        void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            T item = sender as T;
            if (e.PropertyName.Equals("Status", StringComparison.InvariantCultureIgnoreCase))
            {
                // This must be a metacontact if the property change was in status.
                // Change the online count, if necessary.
                MetaContact metacontact = item as MetaContact;
                bool doUpdate = false;
                if (metacontact.OldStatus == ContactStatus.Offline)
                {
                    onlineCount++;
                    doUpdate = true;
                }
                else if (metacontact.Status == ContactStatus.Offline)
                {
                    onlineCount--;
                    doUpdate = true;
                }

                if (doUpdate && PropertyChanged != null)
                {
                    PropertyChanged(this, new PropertyChangedEventArgs("OnlineCount"));
                }
            }

            // If the list isn't sorted or filtered, no rearrangement is necessary
            if (!isSorted && !isFiltered)
            {
                return;
            }

            // Lists of groups can be sorted/filtered by display name and online count,
            // lists of metacontacts by display name and status
            if (e.PropertyName == "DisplayName" || e.PropertyName == "Status" || e.PropertyName.EndsWith("Count"))
            {
                MoveWithinList(new MoveWithinListData(item));
            }
        }

        /// <summary>
        /// A constant value returned by FindInCurrentList
        /// when the requested item is not in the list
        /// </summary>
        private const int ITEM_NOT_IN_LIST = -1;

        /// <summary>
        /// Move an item within the list by its current properties
        /// </summary>
        private void MoveWithinList(MoveWithinListData data)
        {
            if (uiDispatcher == null || uiDispatcher.CheckAccess())
            {
                //lock (this)
                {
                    bool fireItemsChangedEvent = false;
                    int originalPosition, newPosition;
                    // Remove the item at its current position
                    {
                        originalPosition = FindInCurrentList(data.Item);
                        if (originalPosition != ITEM_NOT_IN_LIST)
                        {
                            Items.RemoveAt(originalPosition);
                            fireItemsChangedEvent = true;
                        }
                    }

                    // Reinsert it at its new position
                    {
                        newPosition = PlaceByCurrentComparison(data.Item);
                        if (newPosition != ITEM_NOT_IN_LIST)
                        {
                            Items.Insert(newPosition, data.Item);
                            fireItemsChangedEvent = true;
                        }
                    }

                    if (fireItemsChangedEvent)
                    {
                        // Figure out what to fire
                        if (IsValidPosition(originalPosition) && IsValidPosition(newPosition)
                            && originalPosition != newPosition)
                        {
                            OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));//, newPosition, originalPosition));
                        }
                        else if (IsValidPosition(originalPosition) && !IsValidPosition(newPosition))
                        {
                            OnListChanged(new ListChangedEventArgs(ListChangedType.ItemDeleted, originalPosition));
                        }
                        else if (!IsValidPosition(originalPosition) && IsValidPosition(newPosition))
                        {
                            OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, newPosition));
                        }

                        if (PropertyChanged != null)
                        {
                            PropertyChanged(this, new PropertyChangedEventArgs("Items"));
                        }
                    }
                }
            }
            else
            {
                Console.WriteLine("Dispatching from MoveWithinList");
                uiDispatcher.Dispatch(new MoveWithinListDispatchHandler(MoveWithinList), data);
            }
        }

        /// <summary>
        /// Returns a value indicating whether the given position is valid for the list
        /// </summary>
        private static bool IsValidPosition(int position)
        {
            return position > ITEM_NOT_IN_LIST;
        }

        /// <summary>
        /// Returns the index of a metacontact in the currently displayed list
        /// </summary>
        private int FindInCurrentList(T item)
        {
            return Items.IndexOf(item);
        }

        /// <summary>
        /// Returns the index at which an item would be inserted by the current sort comparison
        /// and current filter
        /// </summary>
        /// <returns>The index at which to insert the item, or -1 if it would not be in the currently filtered list</returns>
        private int PlaceByCurrentComparison(T item)
        {
            if (isFiltered && !currentFilter(item))
            {
                return -1;
            }

            int insertindex = 0;
            if (currentComparison != null)
            {
                // Get the index in the sorted list
                while (insertindex < Count && currentComparison(item, Items[insertindex]) >= 0)
                {
                    insertindex++;
                }
            }
            else
            {
                if (insertIndicies.ContainsKey(item)) // TODO: why...why wouldn't it?
                {
                    while (insertindex < Count &&
                        (insertIndicies.ContainsKey(Items[insertindex]) && insertIndicies[item] >= insertIndicies[Items[insertindex]]))
                    {
                        insertindex++;
                    }
                }
            }

            return insertindex;
        }
        #endregion

        /// <summary>
        /// Provides a delegate for dispatching a list refresh
        /// </summary>
        private delegate void RefreshHandler();

        /// <summary>
        /// If <see cref="NeedsBindingRefresh"/> is true, resets the list bindings and the refresh flag
        /// </summary>
        internal void Refresh()
        {
            if (uiDispatcher == null || uiDispatcher.CheckAccess())
            {
                if (NeedsBindingRefresh)
                {
                    OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
                    NeedsBindingRefresh = false;
                }
            }
            else
            {
                uiDispatcher.Dispatch(new RefreshHandler(Refresh));
            }
        }

        /// <summary>
        /// Searches for the index of the item that has the specified property descriptor with the specified value
        /// </summary>
        protected override int FindCore(PropertyDescriptor prop, object key)
        {
            for (int i = 0; i < Count; i++)
            {
                if (prop.GetValue(this[i]).Equals(key))
                {
                    return i;
                }
            }
            return -1;
        }

        /// <summary>
        /// Sorts the list by the specified property in the specified direction
        /// </summary>
        internal void Sort(Comparison<T> sortCriteria)
        {
            isSorted = true;
            currentComparison = sortCriteria;
            ApplyListAlterations();
        }

        /// <summary>
        /// Removes sorting logic from the list
        /// </summary>
        internal void RemoveSort()
        {
            isSorted = false;
            currentComparison = null;
            ApplyListAlterations();
        }

        /// <summary>
        /// Filters the list contents by the specified predicate
        /// </summary>
        internal void Filter(Predicate<T> filterCriteria)
        {
            isFiltered = true;
            currentFilter = filterCriteria;
            ApplyListAlterations();
        }

        /// <summary>
        /// Removes the current filter from the list
        /// </summary>
        internal void RemoveFilter()
        {
            if (!isFiltered)
            {
                return;
            }

            isFiltered = false;
            currentFilter = null;
            ApplyListAlterations();
        }

        /// <summary>
        /// Provides a delegate for dispatching a list filter/sort operation
        /// </summary>
        private delegate void ApplyListAlterationsDispatchHandler();

        /// <summary>
        /// Applies the current filter and sort to the list
        /// </summary>
        internal void ApplyListAlterations()
        {
            if (uiDispatcher == null || uiDispatcher.CheckAccess())
            {
                List<T> listref = Items as List<T>;

                Items.Clear();
                listref.AddRange(unsortedList);

                if (isFiltered)
                {
                    // Perform filtering on the current item list
                    for (int i = 0; i < Count; i++)
                    {
                        if (!currentFilter(Items[i]))
                        {
                            Items.RemoveAt(i--);
                        }
                    }
                }

                if (isSorted)
                {
                    listref.Sort(currentComparison);
                }

                OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
            }
            else
            {
                uiDispatcher.Dispatch(new ApplyListAlterationsDispatchHandler(ApplyListAlterations));
            }
        }
        #endregion

        #region INotifyPropertyChanged Members
        /// <summary>
        /// Raised when a property on the list changes
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        internal void DumpListState(ICore shaimCore)
        {
            string[] lines = new string[5];
            lines[0] = String.Format("Type parameter: {0}", typeof(T).Name);
            lines[1] = String.Format("Visible count: {0}", Count);
            lines[2] = String.Format("Total count: {0}", unsortedList.Count);
            lines[3] = String.Format("Filtering: {0}", (currentFilter == null) ? "None" : currentFilter.Method.Name);
            lines[4] = String.Format("Comparison: {0}", (currentComparison == null) ? "None" : currentComparison.Method.Name);
            shaimCore.LogMessage(null, lines, ShaimLogLevel.Debug);
        }
    }
}
